package com.hhkj.api.train.common.component.creator;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.hhkj.admin.bank.common.component.checker.BankCreateChecker;
import com.hhkj.admin.bank.common.cont.BankConst;
import com.hhkj.admin.bank.common.cont.QuestionOrderRuleConst;
import com.hhkj.admin.bank.domain.Bank;
import com.hhkj.admin.bank.domain.BankRule;
import com.hhkj.admin.bank.model.extend.QuestionCorrectRule;
import com.hhkj.admin.bank.model.extend.QuestionOrderRule;
import com.hhkj.admin.bank.model.extend.TestQuestionAmountConf;
import com.hhkj.admin.bank.model.res.BankRes;
import com.hhkj.admin.bank.service.extend.BankExtendService;
import com.hhkj.admin.question.common.cont.QuestionTypeEnum;
import com.hhkj.admin.question.common.utils.QuestionListUtils;
import com.hhkj.admin.question.domain.Question;
import com.hhkj.admin.question.model.res.QuestionRes;
import com.hhkj.admin.train.common.cont.TrainConst;
import com.hhkj.admin.train.domain.Train;
import com.hhkj.admin.train.model.req.TrainReq;
import com.hhkj.admin.train.service.base.TrainService;
import com.hhkj.api.bank.service.biz.ApiBankBizService;
import com.hhkj.api.common.session.holder.ApiStudentSessionHolder;
import com.hhkj.api.common.session.holder.model.LoginStudent;
import com.hhkj.api.train.common.bizutils.ApiTrainBizUtils;
import com.hhkj.api.train.common.cont.ApiTrainConst;
import com.hhkj.api.train.common.utils.ApiQuestionCollectionConverter;
import com.hhkj.api.train.common.utils.CorrectUtils;
import com.hhkj.api.train.model.info.req.TrainStageReq;
import com.hhkj.api.train.model.info.res.ApiQuestionRes;
import com.hhkj.api.train.model.info.res.ApiTrainContinuePractiseRes;
import com.hhkj.api.train.model.info.res.ApiTrainPractiseRes;
import com.hhkj.api.train.model.info.res.ApiTrainTestRes;
import com.ruoyi.common.cont.SysConst;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.GlobalException;
import com.ruoyi.common.utils.redis.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * ApiTrainCreateFactory
 *
 * @author HHKJ-SERVER-ZJ
 * @since 2023/5/25 15:51
 */
@Slf4j
@Component
public class ApiTrainCreator {

    @Autowired
    private TrainService trainService;
    @Autowired
    private ApiTrainBizUtils apiTrainBizUtils;
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private ApiBankBizService apiBankBizService;
    @Autowired
    private BankExtendService bankServiceExtend;

    /**
     * 默认练习有效时长
     */
    private final Integer DEFAULT_PRACTISE_MINUTES = 60 * 24;

    /**
     * 创建练习培训信息
     *
     * @param modelCode 练习模式 answer：答题模式；recitation：
     * @return 培训信息
     */
    public ApiTrainPractiseRes createPractiseTrain(String modelCode) {
        LoginStudent currentStudent = ApiStudentSessionHolder.getLoginStudent();
        BankRes bank = apiBankBizService.getPublishBank(BankConst.Type.PRACTISE.getCode());
        bank.orderPractiseQuestionList();
        List<QuestionRes> questionGroupList = bank.getQuestionGroupList();
        Integer totalAmount = QuestionListUtils.countGroupResAmount(questionGroupList);
        TrainReq trainReq = new TrainReq(currentStudent.getId(), bank.getCourseId(), bank.getId(), totalAmount,
                questionGroupList, new Date(), modelCode);

        String trainKey = this.cacheTrain(
                trainReq,
                DEFAULT_PRACTISE_MINUTES,
                TimeUnit.MINUTES,
                String.valueOf(currentStudent.getCourseParentId()),
                String.valueOf(currentStudent.getCourseBankTypeId()),
                String.valueOf(currentStudent.getCourseId()),
                BankConst.Type.PRACTISE.getCode(),
                modelCode,
                String.valueOf(currentStudent.getId())
        );

        List<ApiQuestionRes> apiQuestionResList = ApiQuestionCollectionConverter.convertList(questionGroupList);
        return new ApiTrainPractiseRes(trainKey, DEFAULT_PRACTISE_MINUTES, apiQuestionResList);
    }

    /**
     * 根据题库id获取练习信息
     *
     * @param bankId 题库id信息
     * @return 测试题库信息
     */
    public ApiTrainTestRes createTrainByBankId(Long bankId) {
        // 获取测试题库信息
        BankRes bankRes = apiBankBizService.getBankById(bankId);
        if (ObjectUtil.isNull(bankRes)) {
            throw new GlobalException("题库不存在");
        }
        if (BankConst.Type.TEST.getCode().equals(bankRes.getTypeCode())) {
            // 获取练习题库信息
            BankRes practiseBankRes = apiBankBizService.getBankByParentIdAndTypeCode(bankRes.getParentId(), BankConst.Type.PRACTISE.getCode());
            BankRes totalBankRes = apiBankBizService.getBankById(bankRes.getParentId());
            return this.createTrainTestRes(totalBankRes, practiseBankRes, bankRes);
        } else {
            throw new GlobalException("该题库占不支持该模式");
        }
    }

    /**
     * 获取测试练习信息
     *
     * @param totalBankRes    所有题库
     * @param practiseBankRes 练习题库信息
     * @param testBankRes     测试题库信息
     * @return 测试练习信息
     */
    private ApiTrainTestRes createTrainTestRes(BankRes totalBankRes, BankRes practiseBankRes, BankRes testBankRes) {
        LoginStudent currentStudent = ApiStudentSessionHolder.getLoginStudent();
        List<QuestionRes> questionGroupList = testBankRes.getQuestionGroupList();
        TrainReq trainReq = new TrainReq(currentStudent.getId(), testBankRes.getCourseId(), testBankRes.getId(),
                questionGroupList, new Date());
        // 题目组列表处理
        // 1、题目数量配置

        BankRule rule = testBankRes.getRule();
        trainReq.setPassMark(rule.getTestPassMark());
        questionGroupList
                = this.filterQuestionListGroupByRule(totalBankRes.getQuestionGroupList(), practiseBankRes.getQuestionGroupList(), rule, testBankRes);

        // 2、题目分数配置
        this.confListGroupCorrect(questionGroupList, rule.getTestCorrectConfMap());

        // 3、题目顺序规则
        questionGroupList = this.confListGroupOrder(questionGroupList, rule.getTestOrderRule());
        if (SysConst.Flag.IS.getFlag().equals(rule.getTestShuffleOptionFlag())) {
            this.confOptionOrder(questionGroupList);
        }

        // 统计题目组总分数信息
        BigDecimal totalMark = this.countQuestionGroupListMark(questionGroupList);
        // 设置题库信息
        trainReq.setQuestionGroupList(questionGroupList);
        trainReq.setTotalMark(totalMark);
        trainReq.setTotalAmount(QuestionListUtils.countGroupResAmount(questionGroupList));
        String trainKey = this.cacheTrain(
                trainReq,
                rule.getTestDurationMinute(),
                TimeUnit.MINUTES,
                String.valueOf(currentStudent.getCourseParentId()),
                String.valueOf(currentStudent.getCourseBankTypeId()),
                String.valueOf(currentStudent.getCourseId()),
                totalBankRes.getId().toString(),
                BankConst.Type.TEST.getCode(),
                String.valueOf(currentStudent.getId())
        );

        return new ApiTrainTestRes(trainKey, rule.getTestDurationMinute(), questionGroupList);
    }

    /**
     * 创建测试题库信息
     *
     * @return 测试题库信息
     */
    public ApiTrainTestRes createTestTrain() {
        // 获取测试题库信息
        BankRes testBankRes = apiBankBizService.getPublishBank(BankConst.Type.TEST.getCode());
        // 获取练习题库信息
        BankRes practiseBankRes = apiBankBizService.getPublishBank(BankConst.Type.PRACTISE.getCode());
        BankRes totalBankRes = apiBankBizService.getPublishBank(BankConst.Type.TOTAL.getCode());

        return this.createTrainTestRes(totalBankRes, practiseBankRes, testBankRes);
    }

    /**
     * 配置题目序号信息
     *
     * @param questionGroupList 题目分组列表
     */
    public void confOptionOrder(List<QuestionRes> questionGroupList) {
        for (QuestionRes questionRes : questionGroupList) {
            if (QuestionTypeEnum.CASE.getCode().equals(questionRes.getTypeCode())) {
                for (QuestionRes question : questionRes.getQuestionList()) {
                    question.shuffleOptionList();
                }
            } else {
                questionRes.shuffleOptionList();
            }
        }
    }

    /**
     * 创建继续练习培训信息
     * @isStageAnswer 是否暂存答案后继续练习
     * @return 培训信息
     */
    public ApiTrainContinuePractiseRes createContinuePractiseTrain(boolean isStageAnswer) {
        LoginStudent currentStudent = ApiStudentSessionHolder.getLoginStudent();
        // 获取上次练习培训信息
        Train trainDo = new Train(
                currentStudent.getId(),
                currentStudent.getCourseId(),
                BankConst.Type.PRACTISE.getCode());
        if (isStageAnswer) {
            trainDo.setStatus(SysConst.Flag.IS.getFlag());
        }
        Train trainPo = trainService.getLastTrainPoByDo(trainDo);
        if (ObjectUtil.isNull(trainPo)
                || trainPo.getAnswerAmount().equals(trainPo.getTotalAmount())) {
            throw new GlobalException("没有上次练习记录或上次练习已经结束！");
        }
        // 题库上线校验
        Bank publishBankPo
                = bankServiceExtend.getPublishBank(
                currentStudent.getCourseId(),
                currentStudent.getCourseBankTypeId(),
                BankConst.Type.PRACTISE.getCode());
        if (!trainPo.getBankId().equals(publishBankPo.getId())) {
            throw new GlobalException("题库已更新，请从头开始");
        }

        TrainReq trainReq = BeanUtil.toBean(trainPo, TrainReq.class);
        // 根据培训信息匹配培训题目信息列表
        apiTrainBizUtils.matchTrainQuestionList(trainReq);
        trainReq.setStartTime(new Date());
        // 缓存当前培训信息
        String trainKey = this.cacheTrain(
                trainReq,
                DEFAULT_PRACTISE_MINUTES,
                TimeUnit.MINUTES,
                String.valueOf(currentStudent.getCourseParentId()),
                String.valueOf(currentStudent.getCourseBankTypeId()),
                String.valueOf(currentStudent.getCourseId()),
                trainReq.getTypeCode(),
                trainReq.getPracticeModeCode(),
                String.valueOf(currentStudent.getId()));

        // 题目列表信息处理成结果集列表
        return new ApiTrainContinuePractiseRes(
                trainKey,
                DEFAULT_PRACTISE_MINUTES,
                trainReq.getPracticeModeCode(),
                trainReq.getQuestionGroupList());
    }

    /**
     * 计算统计题目组列表分数信息
     *
     * @param questionGroupList 题目组列表
     * @return 题目组分数总计
     */
    private BigDecimal countQuestionGroupListMark(List<QuestionRes> questionGroupList) {
        BigDecimal totalMark = BigDecimal.ZERO;
        for (QuestionRes questionGroup : questionGroupList) {
            if (QuestionTypeEnum.CASE.getCode().equals(questionGroup.getTypeCode())) {
                for (QuestionRes question : questionGroup.getQuestionList()) {
                    totalMark = totalMark.add(question.getMark());
                }
            } else {
                totalMark = totalMark.add(questionGroup.getMark());
            }
        }
        return totalMark;
    }

    /**
     * 配置题目组顺序
     *
     * @param questionGroupList 题目组列表
     * @param orderRule         排序规则
     */
    private List<QuestionRes> confListGroupOrder(List<QuestionRes> questionGroupList, QuestionOrderRule orderRule) {

        List<QuestionRes> newQuestionGroupList = new ArrayList<>();
        // 3、题目顺序规则
        if (QuestionOrderRuleConst.Type.MANUAL_SORT_TYPE
                .getCode().equals(orderRule.getTypeCode())) {
            Map<String, List<QuestionRes>> questionGroupListGroupTypeCode = questionGroupList.stream()
                    .collect(Collectors.groupingBy(QuestionRes::getTypeCode));
            newQuestionGroupList = new ArrayList<>();
            for (String questionTypeCode : orderRule.getQuestionTypeCodeList()) {
                if (!questionGroupListGroupTypeCode.containsKey(questionTypeCode)) {
                    continue;
                }
                newQuestionGroupList.addAll(questionGroupListGroupTypeCode.get(questionTypeCode));
            }
        } else {
            Collections.shuffle(newQuestionGroupList);
        }
        return newQuestionGroupList;
    }

    /**
     * 配置题目分数信息
     *
     * @param questionGroupList 题目组列表
     * @param correctConfMap    批改规则
     */
    private void confListGroupCorrect(List<QuestionRes> questionGroupList, Map<String, QuestionCorrectRule> correctConfMap) {
        QuestionCorrectRule questionCorrectRule;
        for (QuestionRes questionGroup : questionGroupList) {
            if (QuestionTypeEnum.CASE.getCode().equals(questionGroup.getTypeCode())) {
                for (QuestionRes question : questionGroup.getQuestionList()) {
                    questionCorrectRule = correctConfMap.get(question.getTypeCode());
                    question.setMark(questionCorrectRule.getMark());
                    question.setIncompleteHalfMarkFlag(questionCorrectRule.isIncompleteHalfMarkFlag());
                }
            } else {
                questionCorrectRule = correctConfMap.get(questionGroup.getTypeCode());
                questionGroup.setMark(questionCorrectRule.getMark());
                questionGroup.setIncompleteHalfMarkFlag(questionCorrectRule.isIncompleteHalfMarkFlag());
            }
        }
    }

    /**
     * 根据题目数量配置 过滤出题目组信息
     *
     * @param totalGroupList    全题目题库列表
     * @param practiceGroupList 题目组列表
     * @param rule              选题规则
     * @return 题目组列表
     */
    private List<QuestionRes> filterQuestionListGroupByRule(List<QuestionRes> totalGroupList,
                                                            List<QuestionRes> practiceGroupList,
                                                            BankRule rule,
                                                            Bank bank) {
        List<QuestionRes> newQuestionGroupList = new ArrayList<>();
        // 获取除练习题库外的其余题库信息
        List<QuestionRes> residueGroupList
                = CollectionUtil.subtractToList(totalGroupList, practiceGroupList);

        if (CollectionUtil.isEmpty(bank.getTestAmountConfMap())) {
            List<String> errorList = BankCreateChecker.checkerAndConfTestAmountIfNotError(bank, rule, practiceGroupList, residueGroupList);
            if (CollectionUtil.isNotEmpty(errorList)) {
                log.error(CollectionUtil.join(errorList, ","));
                throw new GlobalException("题库信息有误，请联系管理员");
            }
        }

        // 测试题库配置信息
        Map<String, TestQuestionAmountConf> testAmountConfDetailMap = bank.getTestAmountConfMap();

        // 题目分组
        Map<String, List<QuestionRes>> practiceGroupListGroupByTypeCode = practiceGroupList.stream()
                .collect(Collectors.groupingBy(QuestionRes::getTypeCode));

        Map<String, List<QuestionRes>> residueGroupListGroupByTypeCode = residueGroupList.stream()
                .collect(Collectors.groupingBy(QuestionRes::getTypeCode));

        String typeCode;
        TestQuestionAmountConf amountConf;
        Integer practiceAmount;
        Integer residueAmount;
        int practiceTotalAmount;
        int residueTotalAmount;

        List<QuestionRes> practiceQuestionGroupList;
        List<QuestionRes> residueQuestionGroupList;
        for (Map.Entry<String, TestQuestionAmountConf> testAmountConfDetailMapEntry : testAmountConfDetailMap.entrySet()) {
            typeCode = testAmountConfDetailMapEntry.getKey();
            amountConf = testAmountConfDetailMapEntry.getValue();
            practiceAmount = amountConf.getPractice();
            residueAmount = amountConf.getResidue();
            // 获取题库题目信息
            practiceQuestionGroupList
                    = practiceGroupListGroupByTypeCode.getOrDefault(typeCode, new ArrayList<>());
            residueQuestionGroupList
                    = residueGroupListGroupByTypeCode.getOrDefault(typeCode, new ArrayList<>());

            practiceTotalAmount = practiceQuestionGroupList.size();
            residueTotalAmount = residueQuestionGroupList.size();
            // 检查题目数量是否符合规范
            if (practiceTotalAmount < practiceAmount
                    || residueTotalAmount < residueAmount) {
                throw new GlobalException("题库中" +
                        (practiceTotalAmount < practiceAmount ? "练习题库" : "剩余题目")
                        + QuestionTypeEnum.getNameByCode(typeCode) + "数量不足，请联系管理员");
            }
            // 打乱题目
            Collections.shuffle(practiceQuestionGroupList);
            Collections.shuffle(residueQuestionGroupList);
            // 抽取题目
            newQuestionGroupList.addAll(practiceQuestionGroupList.subList(0, practiceAmount));
            newQuestionGroupList.addAll(residueQuestionGroupList.subList(0, residueAmount));
        }
        return newQuestionGroupList;
    }

    /**
     * 缓存培训信息
     *
     * @param train          培训信息
     * @param timeout        超时时长
     * @param timeUnit       时间单位
     * @param keyMiddleArray 保存键内容信息
     * @return 缓存的键信息
     */
    private String cacheTrain(final Train train, final Integer timeout, final TimeUnit timeUnit, final String... keyMiddleArray) {
        String cacheKeyPrefix = RedisUtils.createKeyPrefix(
                ApiTrainConst.REDIS_CACHE_KEY_PREFIX, keyMiddleArray);
        redisCache.deleteBatchObject(cacheKeyPrefix);
        UUID uuid = UUID.randomUUID();
        String key = cacheKeyPrefix + uuid;
        redisCache.setCacheObject(cacheKeyPrefix + uuid, train, timeout, timeUnit);
        return key;
    }

    /**
     * 暂存答案
     * @param req
     */
    public void stageTrain(TrainStageReq req) {
        TrainReq trainReq = redisCache.getCacheObject(req.getTrainKey());
        if (ObjectUtil.isNull(trainReq)) {
            throw new GlobalException("培训信息已过期或者不存在！");
        }

        // 1-答案分组，取出题目组信息
        Map<Long, String> answerMapByQuestionId = req.getAnswerMapById();
        List<QuestionRes> questionGroupList = trainReq.getQuestionGroupList();
        for (QuestionRes questionRes : questionGroupList) {
            if (QuestionTypeEnum.CASE.getCode().equals(questionRes.getTypeCode())) {
                for (Question question : questionRes.getQuestionList()) {
                    question.setStudentAnswer(answerMapByQuestionId.getOrDefault(question.getId(), StrUtil.EMPTY));
                }
            } else {
                questionRes.setStudentAnswer(answerMapByQuestionId.getOrDefault(questionRes.getId(), StrUtil.EMPTY));
            }
        }
        // 5-保存培训信息
        trainReq.setAnswerList(CorrectUtils.listStudentAnswerFromGroup(trainReq.getQuestionGroupList()));
        // 暂时保存
        trainReq.setStatus(SysConst.Flag.IS.getFlag());
        trainReq.setSubmitTime(new Date());
        int currentTimeDurationSecond = (int) DateUtil.between(trainReq.getStartTime(), trainReq.getSubmitTime(), DateUnit.SECOND);
        int lastTimeDurationSecond = ObjectUtil.isNull(trainReq.getDurationSecond()) ? 0 : trainReq.getDurationSecond();
        trainReq.setDurationSecond(lastTimeDurationSecond + currentTimeDurationSecond);
        if (ObjectUtil.isNull(trainReq.getId())) {
            trainReq.setCreateBy(ApiStudentSessionHolder.getStudentId());
            trainReq.setCreateTime(new Date());
            trainService.save(trainReq);
        } else {
            trainReq.setUpdateBy(ApiStudentSessionHolder.getStudentId());
            trainReq.setUpdateTime(new Date());
            trainReq.setDisableUpdateByFill(true);
            trainService.updateById(trainReq);
        }
    }
}
